<template>
  <div class="hv-color-picker">
    <div
      class="hv-color-picker-saturation-value"
      ref="saturationValueRef"
      @mousedown="mousedownSV"
    >
      <div :style="`background-color: hsl(${hue}, 100%, 50%);`">
        <div class="hv-color-picker-point" :style="pointStyle"></div>
      </div>
      <div class="hv-color-picker-saturation-value2"></div>
      <div class="hv-color-picker-saturation-value3"></div>
    </div>
    <div class="hv-color-picker-select-middle">
      <div style="flex: auto">
        <div
          class="hv-color-picker-hue-slider"
          ref="hueSliderRef"
          @mousedown="mousedownHue"
        >
          <div class="hv-color-picker-slider" :style="hueSliderStyle"></div>
        </div>
        <div
          class="hv-color-picker-alpha-slider"
          ref="alphaSliderRef"
          @mousedown="mousedownAlpha"
        >
          <div class="hv-color-picker-slider" :style="alphaSliderStyle"></div>
          <div
            :style="`background: linear-gradient(to right, rgba(0,0,0,0), ${colorObj.rgb});width: 100%;height: 100%`"
          ></div>
        </div>
      </div>
      <div class="hv-color-picker-color-diamond">
        <div
          :style="`background-color: ${colorObj.rgba};width: 100%;height: 100%;box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .15), inset 0 0 4px rgba(0, 0, 0, .25);`"
        ></div>
      </div>
    </div>
    <div class="hv-color-picker-color-value">
      <div class="hv-color-picker-hex">
        <label>
          <input :value="colorObj.hex8" @input="hexChange" spellcheck="false" />
        </label>
        <p>Hex</p>
      </div>
      <div class="hv-color-picker-rgba-r">
        <label>
          <input :value="red" @blur="redChange" />
        </label>
        <p>R</p>
      </div>
      <div class="hv-color-picker-rgba-g">
        <label>
          <input :value="green" @blur="greenChange" />
        </label>
        <p>G</p>
      </div>
      <div class="hv-color-picker-rgba-b">
        <label>
          <input :value="blue" @blur="blueChange" />
        </label>
        <p>B</p>
      </div>
      <div class="hv-color-picker-rgba-a">
        <label>
          <input :value="alpha" @blur="alphaChange" />
        </label>
        <p>A</p>
      </div>
    </div>
    <ul class="hv-color-picker-preset">
      <li
        v-for="(item,index) in preset"
        :key="index"
        :style="`background-color: ${item}`"
        @click="presetChange(item)"
      ></li>
    </ul>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, computed, watch, type PropType } from "vue";
import ColorPickerUtil from "./color-picker-util";

export default defineComponent({
  name: "ColorPicker",
  props: {
    color: {
      type: String,
      default: "rgba(0,0,0,1)",
    },
    format: {
      type: String,
      default: "rgba",
    },
    preset: {
      type: Array as PropType<String[]>,
      default: () => {
        return [
          "#D0021B",
          "#F5A623",
          "#F8E71C",
          "#8B572A",
          "#7ED321",
          "#417505",
          "#BD10E0",
          "#9013FE",
          "#4A90E2",
          "#50E3C2",
          "#B8E986",
          "#000000",
          "#4A4A4A",
          "#9B9B9B",
          "#FFFFFF",
        ];
      },
    },
  },
  emits: ["update:color", "change"],
  setup(props, context) {
    const saturationValueRef = ref();
    const hueSliderRef = ref();
    const alphaSliderRef = ref();

    let pointStyle = ref("top: 25%;left: 80%;");
    let hueSliderStyle = ref("left: 0;");
    let alphaSliderStyle = ref("left: calc(100% - 6px);");

    let hue = ref(0);
    let saturation = ref(1);
    let value = ref(1);

    let red = ref(0);
    let green = ref(0);
    let blue = ref(0);

    let alpha = ref(1);

    const doUpdateView = () => {
      let { h, s, v } = rgb2hsv(red.value, green.value, blue.value);

      hue.value = h;
      saturation.value = s;
      value.value = v;

      // 移动背景板圆圈
      pointStyle.value = `top: ${100 - v * 100}%;left: ${s * 100}%;`;
      // 移动色调滑块
      hueSliderStyle.value = `left: ${(hue.value / 360) * 100}%;`;

      // 移动透明度滑块
      alphaSliderStyle.value = `left: ${
        alpha.value >= 1 ? "calc(100% - 6px)" : alpha.value * 100 + "%"
      };`;
    };

    /**
     * 更新
     */
    const doUpdate = () => {
      let val = "";
      if (props.format === "rgba") {
        val = `rgba(${red.value},${green.value},${blue.value},${alpha.value})`;
      } else if (props.format === "hex8") {
        val = rgba2hex(red.value, green.value, blue.value, alpha.value);
      }

      doUpdateView();

      context.emit("update:color", val);
      context.emit("change", val);
    };

    const colorObj = computed(() => {
      const r = red.value;
      const g = green.value;
      const b = blue.value;
      const a = alpha.value;
      const h = hue.value;
      const s = saturation.value;
      const v = value.value;
      return {
        rgb: `rgba(${r},${g},${b})`,
        rgba: `rgba(${r},${g},${b},${a})`,
        hex6: rgba2hex(r, g, b),
        hex8: rgba2hex(r, g, b, a),
        hsv: `hsv(${h},${s},${v})`,
      };
    });

    // 输入框值变化,限制输入的值
    const hexChange = (e) => {
      const v = e.target.value;
      if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(v)) {
        const { r, g, b, a } = ColorPickerUtil.hex2rgba(v);
        red.value = r;
        green.value = g;
        blue.value = b;
        alpha.value = a;
        doUpdate();
      }
    };

    const redChange = (e) => {
      const v = e.target.value;
      if (v !== "") {
        v > 255 && (red.value = 255);
        v < 0 && (red.value = 0);
        v >= 0 && v <= 255 && (red.value = parseInt(v));
        doUpdate();
      }
    };

    const greenChange = (e) => {
      const v = e.target.value;
      if (v !== "") {
        v > 255 && (green.value = 255);
        v < 0 && (green.value = 0);
        v >= 0 && v <= 255 && (green.value = parseInt(v));
        doUpdate();
      }
    };

    const blueChange = (e) => {
      const v = e.target.value;
      if (v !== "") {
        v > 255 && (blue.value = 255);
        v < 0 && (blue.value = 0);
        v >= 0 && v <= 255 && (blue.value = parseInt(v));
        doUpdate();
      }
    };

    const alphaChange = (e) => {
      let v = e.target.value;
      if (v !== "") {
        v = parseFloat(v);
        alpha.value = v;
        v > 1 && (alpha.value = 1);
        v < 0 && (alpha.value = 0);
        v >= 0 && v <= 1 && (alpha.value = v);
        doUpdate();
      }
    };

    // 点击预设方块事件
    const presetChange = (item) => {
      if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(item)) {
        const { r, g, b, a } = ColorPickerUtil.hex2rgba(item);
        red.value = r;
        green.value = g;
        blue.value = b;
        alpha.value = a;
      }
      doUpdate();
    };

    // 饱和度和亮度
    const handleChangeSV = (e) => {
      let w = saturationValueRef.value.clientWidth;
      let h = saturationValueRef.value.clientHeight;
      let x = e.pageX - saturationValueRef.value.getBoundingClientRect().left;
      let y = e.pageY - saturationValueRef.value.getBoundingClientRect().top;
      x = x < w && x > 0 ? x : x > w ? w : 0;
      y = y < h && y > 0 ? y : y > h ? h : 0;
      // 计算饱和度和亮度
      saturation.value = Math.floor((x / w) * 100 + 0.5) / 100;
      value.value = Math.floor((1 - y / h) * 100 + 0.5) / 100;
      // hsv转化为rgb
      const { r, g, b } = hsv2rgb(hue.value, saturation.value, value.value);
      red.value = r;
      green.value = g;
      blue.value = b;
      // 移动背景板圆圈
      pointStyle.value = `top: ${y}px;left: ${x}px;`;

      doUpdate();
    };

    const mousedownSV = (e) => {
      // 鼠标按下计算饱和度和亮度并添加事件
      handleChangeSV(e);
      // 添加整个页面的鼠标事件
      window.addEventListener("mousemove", handleChangeSV);
      window.addEventListener("mouseup", mouseupSV);
    };

    const mouseupSV = () => {
      // 鼠标松开后移除事件
      window.removeEventListener("mousemove", handleChangeSV);
      window.removeEventListener("mouseup", mouseupSV);
    };

    // 色调
    const handleChangeHue = (e) => {
      let w = hueSliderRef.value.clientWidth;
      let x = e.pageX - saturationValueRef.value.getBoundingClientRect().left;
      x = x < w && x > 0 ? x : x > w ? w : 0;
      // 计算色调
      hue.value = Math.floor((x / w) * 360 + 0.5);
      // hsv转化为rgb
      const { r, g, b } = hsv2rgb(hue.value, saturation.value, value.value);
      red.value = r;
      green.value = g;
      blue.value = b;
      // 移动滑块
      hueSliderStyle.value = `left: ${x >= w - 6 ? w - 6 : x}px;`;
    };

    const mousedownHue = (e) => {
      handleChangeHue(e);
      window.addEventListener("mousemove", handleChangeHue);
      window.addEventListener("mouseup", mouseupHue);
      doUpdate();
    };

    const mouseupHue = () => {
      window.removeEventListener("mousemove", handleChangeHue);
      window.removeEventListener("mouseup", mouseupHue);
    };

    // 透明度
    const handleChangeAlpha = (e) => {
      let w = alphaSliderRef.value.clientWidth;
      let x = e.pageX - saturationValueRef.value.getBoundingClientRect().left;
      x = x < w && x > 0 ? x : x > w ? w : 0;
      // 计算透明度
      alpha.value = Math.floor((x / w) * 100 + 0.5) / 100;
      // 移动滑块
      alphaSliderStyle.value = `left: ${x >= w - 6 ? w - 6 : x}px;`;

      doUpdate();
    };

    const mousedownAlpha = (e) => {
      handleChangeAlpha(e);
      window.addEventListener("mousemove", handleChangeAlpha);
      window.addEventListener("mouseup", mouseupAlpha);
    };

    const mouseupAlpha = () => {
      window.removeEventListener("mousemove", handleChangeAlpha);
      window.removeEventListener("mouseup", mouseupAlpha);
    };

    const hsv2rgb = (h, s, v) => {
      h === 360 && (h = 0);
      let i = Math.floor(h / 60) % 6;
      let f = h / 60 - i;
      let p = v * (1 - s);
      let q = v * (1 - s * f);
      let t = v * (1 - s * (1 - f));
      let r, g, b;
      if (i === 0) {
        r = v;
        g = t;
        b = p;
      } else if (i === 1) {
        r = q;
        g = v;
        b = p;
      } else if (i === 2) {
        r = p;
        g = v;
        b = t;
      } else if (i === 3) {
        r = p;
        g = q;
        b = v;
      } else if (i === 4) {
        r = t;
        g = p;
        b = v;
      } else if (i === 5) {
        r = v;
        g = p;
        b = q;
      }
      r = Math.floor(r * 255 + 0.5);
      g = Math.floor(g * 255 + 0.5);
      b = Math.floor(b * 255 + 0.5);
      return { r, g, b };
    };

    const rgb2hsv = (r, g, b) => {
      let r1 = r / 255;
      let g1 = g / 255;
      let b1 = b / 255;
      let cmax = Math.max(r1, g1, b1);
      let cmin = Math.min(r1, g1, b1);
      let d = cmax - cmin;
      let h, s, v;
      if (d === 0) {
        h = 0;
      } else if (cmax === r1) {
        h = ((60 * (g1 - b1)) / d + 360) % 360;
      } else if (cmax === g1) {
        h = 60 * ((b1 - r1) / d + 2);
      } else if (cmax === b1) {
        h = 60 * ((r1 - g1) / d + 4);
      }
      if (cmax === 0) {
        s = 0;
      } else {
        s = d / cmax;
      }
      v = cmax;
      h = Math.floor(h + 0.5);
      s = Math.floor(s * 100 + 0.5) / 100;
      v = Math.floor(v * 100 + 0.5) / 100;
      return { h, s, v };
    };

    const rgba2hex = (r, g, b, a: any = 1) => {
      r = parseInt(r);
      let r1 = r.toString(16).length !== 2 ? "0" + r.toString(16) : r.toString(16);
      g = parseInt(g);
      let g1 = g.toString(16).length !== 2 ? "0" + g.toString(16) : g.toString(16);
      b = parseInt(b);
      let b1 = b.toString(16).length !== 2 ? "0" + b.toString(16) : b.toString(16);
      a = parseFloat(a);
      let a1 = "";
      if (a !== 1) {
        const temp = Math.floor(256 * a);
        a1 = temp.toString(16).length !== 2 ? "0" + temp.toString(16) : temp.toString(16);
      }
      return `#${r1}${g1}${b1}${a1}`.toUpperCase();
    };

    watch(
      () => props.color,
      () => {
        let { r, g, b, a } = ColorPickerUtil.parseColor(props.color, props.format);
        if (
          r !== red.value &&
          g !== green.value &&
          b !== blue.value &&
          a !== alpha.value
        ) {
          red.value = r;
          green.value = g;
          blue.value = b;
          alpha.value = a;

          doUpdateView();
        }
      },
      { immediate: true }
    );

    return {
      saturationValueRef,
      hueSliderRef,
      alphaSliderRef,
      pointStyle,
      hueSliderStyle,
      alphaSliderStyle,
      hue,
      saturation,
      value,
      red,
      green,
      blue,
      alpha,
      colorObj,
      hexChange,
      redChange,
      greenChange,
      blueChange,
      alphaChange,
      presetChange,
      mousedownSV,
      mousedownHue,
      mousedownAlpha,
    };
  },
});
</script>

<style lang="less" scoped>
@import "./ColorPicker.less";
</style>
